package io.github.harmonly.skyblock.data;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.github.harmonly.skyblock.manager.WorldManager;
import io.github.harmonly.skyblock.type.Rank;
import io.github.harmonly.skyblock.util.*;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;

import java.util.UUID;

public class PlayerData extends StableObject {

    private static final String DEFAULT_RANK = "NONE";
    private final Player player;
    private int monthlyPackageRankExpiredTaskId;
    private Scoreboard scoreboard;

    /**
     * @param raw A JSON object representing a Hypixel player, as returned from the API. If this
     *            object is valid, it can be retrieved later via {@link #getRaw()}.
     */
    public PlayerData(JsonElement raw, Player player) {
        super(raw);
        this.player = player;
        scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
        player.setScoreboard(scoreboard);
    }

    public void init() {
        addProperty("uuid", Utilities.simplifiedUuid(player.getUniqueId()));
        addProperty("displayname", player.getDisplayName());
        addProperty("playername", player.getName());

        // first login
        if (!hasProperty("firstLogin")) {
            addProperty("firstLogin", System.currentTimeMillis());
        }

        // op
        updateOp();

        // monthly package rank remove timer
        runMonthlyPackageRankExpiredTimer();

        // rank prefix
        updateDisplay();

        teleportTo("worlds/main_lobby");
    }

    public void disable() {
        player.setDisplayName(getName());
    }

    public Player getPlayer() {
        return this.player;
    }

    public UUID getUuid() {
        String uuidStr = getStringProperty("uuid", null);
        return uuidStr != null ? Utilities.uuidFromString(uuidStr) : null;
    }

    public String getName() {
        // Attempt to get their display name
        String displayName = getStringProperty("displayname", null);
        if (displayName != null) {
            return displayName;
        }

        // Fallback to their most recently-known alias
        JsonArray knownAliases = getArrayProperty("knownAliases");
        if (knownAliases != null && knownAliases.size() > 0) {
            return knownAliases.get(knownAliases.size() - 1).getAsString();
        }

        // Fallback to lowercase variants of their name
        return getStringProperty("playername", getStringProperty("username", null));
    }

    /**
     * @return The color of the player's "+"s if they have MVP+ or MVP++. If they do not have
     * either rank, or if they have not selected a color, {@code RED} is returned as the
     * default.
     */
    public String getSelectedPlusColor() {
        return getStringProperty("rankPlusColor", "RED");
    }

    /**
     * Note, returned colors use the names seen in <a href=https://minecraft.wiki/w/Formatting_codes#Color_codes>this
     * table</a>, in all uppercase. For example, {@code DARK_BLUE} and {@code GRAY}.
     *
     * @return The color of the player's name tag if they have MVP++. Defaults to {@code GOLD}.
     */
    public String getSuperstarTagColor() {
        return getStringProperty("monthlyRankColor", "GOLD");
    }

    /**
     * Returns the most privileged network rank that the player has.
     * <p><br>
     * Example: If... <ul>
     * <li>A player's base rank is MVP+ ({@code MVP_PLUS})</li>
     * <li>They have a subscription for MVP++ ({@code SUPERSTAR})</li>
     * <li>They are a staff member with the moderator rank ({@code MODERATOR})</li>
     * </ul>
     * ...then this method will return {@code MODERATOR}, because it has the highest permission
     * level of the three ranks.
     *
     * @return The most privileged network rank that the player has, or {@code NONE} if they do
     * not have any.
     * @apiNote Display prefixes are not considered, as they have no effect on permissions.
     * Examples include "OWNER" and "MOJANG".
     * @see <a href=https://github.com/HypixelDev/PublicAPI/wiki/Common-Questions#how-do-i-get-a-players-rank-prefix>"How
     * do I get a player's rank prefix?"</a>
     */
    public String getHighestRank() {
        if (hasRankInField("rank")) {
            return getStringProperty("rank", DEFAULT_RANK);
        } else if (hasRankInField("monthlyPackageRank")) {
            return getStringProperty("monthlyPackageRank", DEFAULT_RANK);
        } else if (hasRankInField("packageRank")) {
            return getStringProperty("packageRank", DEFAULT_RANK);
        }
        return DEFAULT_RANK;
    }

    /**
     * Helper method for checking if a rank-related field contains a non-default rank.
     *
     * @param name The name/json-path of the field to check.
     * @return Whether or not the field contains a non-default rank value.
     * @implNote {@code false} if {@code null}, {@code NONE}, or {@code NORMAL}
     */
    protected boolean hasRankInField(String name) {
        String value = getStringProperty(name, DEFAULT_RANK);
        return !value.isEmpty() && !value.equals("NONE") && !value.equals("NORMAL");
    }

    /**
     * @return {@code true} if the player has a network rank (e.g. {@code VIP}, {@code MVP++},
     * {@code MODERATOR}, etc).
     * @apiNote Display prefixes are not considered, as they are technically not ranks. Examples
     * include "OWNER" and "MOJANG".
     */
    public boolean hasRank() {
        return !getHighestRank().equals(DEFAULT_RANK);
    }

    /**
     * @return {@code true} if the player is a member of the <a href=https://twitter.com/hypixelbuilders>Hypixel
     * Build Team</a>. Otherwise {@code false}.
     */
    public boolean isOnBuildTeam() {
        return getBooleanProperty("buildTeam", false)
                || getBooleanProperty("buildTeamAdmin", false);
    }

    /**
     * @return {@code true} if the player could be identified by the API. Otherwise {@code
     * false}.
     */
    public boolean exists() {
        return getUuid() != null;
    }

    public boolean onLobby() {
        return player.getWorld().getName().contains("lobby");
    }

    public boolean onSkyBlock() {
        return player.getWorld().getName().contains("skyblock");
    }

    public void updateOp() {
        if (player.isOp()) {
            addProperty("rank", "ADMIN");
        }
        if (hasProperty("rank")) {
            if (!player.isOp()) {
                addProperty("rank", "NORMAL");
            }
            player.setOp(getHighestRank().equals("ADMIN"));
        }
    }

    public void setDisplay(String prefix, ChatColor color, String suffix) {
        String display = prefix + color + getName() + suffix;
        player.setDisplayName(display);
        player.setPlayerListName(display);
        Team team = scoreboard.getTeam("NAME_TAG");
        if (team == null) {
            team = scoreboard.registerNewTeam("NAME_TAG");
        }
        team.addEntry(player.getName());
        team.setPrefix(prefix);
        team.setColor(color);
        team.setSuffix(suffix);
    }

    public void updateDisplay() {
        // don't change prefix when playing skyblock
        if (onSkyBlock()) {
            return;
        }

        // rank
        String prefix = Rank.getRank(DEFAULT_RANK).getDisplay();
        Rank rank = Rank.getRank(getHighestRank());
        if (hasProperty("prefix")) {
            prefix = getStringProperty("prefix", "") + " ";
        } else if (hasRank()) {
            String plusColor = rank.equals(Rank.SUPERSTAR) ? getSuperstarTagColor() : getSelectedPlusColor();
            prefix = rank.getDisplay(plusColor) + " ";
        }
        setDisplay(prefix, rank.getColor(), "");
    }

    public void setRank(String rank) {
        addProperty("rank", rank);
        updateDisplay();
    }

    public void setPackageRank(String rank) {
        addProperty("packageRank", rank);
        addProperty("newPackageRank", rank);
        if (rank.contains("PLUS")) {
            addProperty("rankPlusColor", getSelectedPlusColor());
        }
        updateDisplay();
    }

    public void setMonthlyPackageRank(int day) {
        addProperty("monthlyPackageRank", "SUPERSTAR");
        addProperty("monthlyRankColor", getSuperstarTagColor());
        addProperty("monthlyPackageRankExpired", System.currentTimeMillis() + TimeHelper.MILLIS_PER_REAL_DAY * day);
        updateDisplay();
        runMonthlyPackageRankExpiredTimer();
    }

    public void removeMonthlyPackageRank() {
        addProperty("monthlyPackageRank", "NONE");
        removeProperty("monthlyPackageRankExpired");
        updateDisplay();
    }

    public void runMonthlyPackageRankExpiredTimer() {
        long t = getLongProperty("monthlyPackageRankExpired", 0) - System.currentTimeMillis();
        if (t > 0) {
            Scheduler.cancelTask(monthlyPackageRankExpiredTaskId);
            monthlyPackageRankExpiredTaskId = Scheduler.scheduleSyncDelayedTask(this::removeMonthlyPackageRank, t / 1000 * 20);
        } else {
            removeMonthlyPackageRank();
        }
    }

    public void removeRank() {
        removeProperty("rank");
        removeProperty("packageRank");
        removeProperty("newPackageRank");
        removeProperty("monthlyPackageRank");
        removeProperty("rankPlusColor");
        removeProperty("monthlyRankColor");
        removeProperty("monthlyPackageRankExpired");
        updateDisplay();
    }

    public JsonObject getProfiles() {
        return getObjectProperty("stats.SkyBlock.profiles");
    }

    public void selectProfile(String profileId) {
        JsonObject profiles = getProfiles();
        if (profiles == null || profiles.isEmpty()) {
            return;
        }
        StableObject object = new StableObject(getProfiles());
        for (String id : profiles.keySet()) {
            object.addProperty(id + ".selected", false);
        }
        object.addProperty(Utilities.simplifiedUuid(profileId) + ".selected", true);
    }

    public JsonElement getSelectedProfile() {
        JsonObject profiles = getProfiles();
        if (profiles == null || profiles.isEmpty()) {
            return new JsonObject();
        }

        StableObject object = new StableObject(profiles);
        String profileId = null;
        for (String id : profiles.keySet()) {
            profileId = id;
            if (object.getBooleanProperty(id + ".selected", false)) {
                break;
            }
        }

        return profileId != null ? JavaUtil.readJson("data/profile/" + Utilities.uuidFromString(profileId) + ".json") : new JsonObject();
    }

    public void deleteProfile(String profileId) {
        removeProperty("stats.SkyBlock.profiles." + Utilities.simplifiedUuid(profileId));
        String targetDir = JavaUtil.DATA_FOLDER + "data/island/" + profileId;
        JavaUtil.deleteDirectory(targetDir);
    }

    public void teleportTo(String worldName) {
        World world = WorldManager.getWorld(worldName);
        if (world != null) {
            player.teleport(world.getSpawnLocation().add(0.5, 0, 0.5));
            player.setGameMode(GameMode.ADVENTURE);
            player.setInvulnerable(true);
            if (!getHighestRank().equals(DEFAULT_RANK)) {
                player.setAllowFlight(true);
                player.setFlying(true);
            }
            updateDisplay();
        }
    }

    @Override
    public String toString() {
        return exists()
                ? "Player" + getRaw()
                : "Player{exists=false}";
    }
}
